#include "napi/native_api.h"
#include "serial_port.h"
#include <string>
#include <vector>
#include <memory>

#define DEFAULT_AT_COMMAND_READ_TIMEOUT_MS 10000

static std::unique_ptr<SerialPort> g_serialPortInstance;


static bool g_scanParamsSuccessfullySet = false;

static std::string NapiStringToStdString(napi_env env, napi_value napi_str) {
    size_t len;
    napi_get_value_string_utf8(env, napi_str, nullptr, 0, &len);
    std::vector<char> buf(len + 1);
    napi_get_value_string_utf8(env, napi_str, buf.data(), len + 1, &len);
    return std::string(buf.data());
}

struct AsyncConnectContext {
    napi_async_work work;
    napi_deferred deferred;
    std::string portName;
    int baudRate;
    bool success;
    std::string error_message;
};

void ExecuteConnect(napi_env env, void* data) {
    AsyncConnectContext* context = static_cast<AsyncConnectContext*>(data);
    if (!g_serialPortInstance) {
        g_serialPortInstance = std::make_unique<SerialPort>();
    }
    int fd = g_serialPortInstance->openPort(context->portName, context->baudRate);
    context->success = (fd != -1);
    if (!context->success) {
        context->error_message = "Failed to open serial port " + context->portName;
    }
}

void CompleteConnect(napi_env env, napi_status status, void* data) {
    AsyncConnectContext* context = static_cast<AsyncConnectContext*>(data);
    if (status != napi_ok) {
        context->success = false;
        context->error_message = "Async connect work failed.";
    }

    if (context->success) {
        napi_value undefined;
        napi_get_undefined(env, &undefined);
        napi_resolve_deferred(env, context->deferred, undefined);
    } else {
        napi_value error_msg_val;
        napi_create_string_utf8(env, context->error_message.c_str(), context->error_message.length(), &error_msg_val);
        napi_value error_obj;
        napi_create_error(env, nullptr, error_msg_val, &error_obj);
        napi_reject_deferred(env, context->deferred, error_obj);
    }
    napi_delete_async_work(env, context->work);
    delete context;
}

static napi_value ConnectSerial(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    AsyncConnectContext* context = new AsyncConnectContext();
    context->portName = NapiStringToStdString(env, args[0]);
    napi_get_value_int32(env, args[1], &context->baudRate);

    napi_value promise;
    napi_create_promise(env, &context->deferred, &promise);

    napi_value resource_name;
    napi_create_string_utf8(env, "ConnectSerialWork", NAPI_AUTO_LENGTH, &resource_name);
    napi_create_async_work(env, nullptr, resource_name, ExecuteConnect, CompleteConnect, context, &context->work);
    napi_queue_async_work(env, context->work);

    return promise;
}



struct AsyncDisconnectContext {
    napi_async_work work;
    napi_deferred deferred;
};

void ExecuteDisconnect(napi_env env, void* data) {
    if (g_serialPortInstance) {
        g_serialPortInstance->closePort();
        g_serialPortInstance.reset();
    }
    

    g_scanParamsSuccessfullySet = false;
}

void CompleteDisconnect(napi_env env, napi_status status, void* data) {
    AsyncDisconnectContext* context = static_cast<AsyncDisconnectContext*>(data);
    napi_value undefined;
    napi_get_undefined(env, &undefined); 

    napi_resolve_deferred(env, context->deferred, undefined);
    napi_delete_async_work(env, context->work);
    delete context;
}

static napi_value DisconnectSerial(napi_env env, napi_callback_info info) {
    AsyncDisconnectContext* context = new AsyncDisconnectContext();
    napi_value promise;
    napi_create_promise(env, &context->deferred, &promise);

    napi_value resource_name;
    napi_create_string_utf8(env, "DisconnectSerialWork", NAPI_AUTO_LENGTH, &resource_name);
    napi_create_async_work(env, nullptr, resource_name, ExecuteDisconnect, CompleteDisconnect, context, &context->work);
    napi_queue_async_work(env, context->work);

    return promise;
}

struct AsyncSendCommandContext {
    napi_async_work work;
    napi_deferred deferred;
    std::string command_to_send;
    std::string original_command_name;
    std::string response_data;
    bool success;
    std::string error_message;
    int read_timeout_ms;
};

void ExecuteSendCommand(napi_env env, void* data) {
    AsyncSendCommandContext* context = static_cast<AsyncSendCommandContext*>(data);
    context->success = false;

    if (!g_serialPortInstance) {
        context->error_message = "Serial port not open.";
        return;
    }
    
    if (context->original_command_name == "SLESTARTSCAN" ||
        context->original_command_name == "SLECONN" ||
        context->original_command_name == "SSAPCREGCBK" ||
        context->original_command_name == "SSAPCFNDSTRU" ||
        context->original_command_name == "SSAPCWRITECMD" ||
        context->original_command_name == "SSAPCREADREQ") {
        if (!g_scanParamsSuccessfullySet) {
            context->error_message = "Scan parameters not set successfully. Please execute AT+SLESETSCANPAR first.";
            

            return;
        }
    }
    
    ssize_t written = g_serialPortInstance->writeData(context->command_to_send.c_str(), context->command_to_send.length());
    if (written < 0) {
        context->error_message = "Failed to write to serial port.";
        return;
    }

    char buffer[512]; 

    std::string full_response;
    int attempts = 5;
    context->read_timeout_ms = (context->read_timeout_ms > 0) ? context->read_timeout_ms : DEFAULT_AT_COMMAND_READ_TIMEOUT_MS; 
    int single_read_timeout = context->read_timeout_ms / (attempts > 0 ? attempts : 1);
    if (single_read_timeout < 100) single_read_timeout = 100;

    for (int i = 0; i < attempts; ++i) {
        ssize_t bytesRead = g_serialPortInstance->readData(buffer, sizeof(buffer) -1, single_read_timeout);
        if (bytesRead > 0) {
            buffer[bytesRead] = '\0';
            full_response.append(buffer);
            if (full_response.find("OK\r\n") != std::string::npos ||
                full_response.find("ERROR\r\n") != std::string::npos ) { 
                break;
            }
        } else if (bytesRead == 0) { 
            if (i == 0 && full_response.empty()) { 
                 context->error_message = "Timeout waiting for response for command: " + context->command_to_send;
                 return;
            }
            break; 
        } else { 
            context->error_message = "Error reading from serial port for command: " + context->command_to_send;
            return;
        }
    }
    
    context->response_data = full_response;
    if (context->response_data.find("ERROR") == std::string::npos) {
        if (context->response_data.find("OK") != std::string::npos || 
            (!full_response.empty() && context->command_to_send.find("READREQ") != std::string::npos) || 
            (context->command_to_send.find("CONN") != std::string::npos )
            ) {
             context->success = true;

             if (context->original_command_name == "SLESETSCANPAR") {
                 g_scanParamsSuccessfullySet = true;
             }
        } else if (full_response.empty() && written > 0) { 
             context->success = true; 
             context->response_data = "SENT_NO_RESPONSE_OK"; 
        }
         else {
             context->error_message = "Command " + context->command_to_send + " did not return OK. Response: " + context->response_data;

             if (context->original_command_name == "SLESETSCANPAR") {
                 g_scanParamsSuccessfullySet = false;
             }
        }
    } else {
        context->error_message = "Command " + context->command_to_send + " returned ERROR. Response: " + context->response_data;

        if (context->original_command_name == "SLESETSCANPAR") {
            g_scanParamsSuccessfullySet = false;
        }
    }
}

void CompleteSendCommand(napi_env env, napi_status status, void* data) {
    AsyncSendCommandContext* context = static_cast<AsyncSendCommandContext*>(data);
    if (status != napi_ok) { 
        context->success = false;
        if (context->error_message.empty()){
            context->error_message = "Async send command work failed.";
        }
    }

    if (context->success) {
        napi_value response_val;
        napi_create_string_utf8(env, context->response_data.c_str(), context->response_data.length(), &response_val);
        napi_resolve_deferred(env, context->deferred, response_val);
    } else {
        napi_value error_msg_val;
        napi_create_string_utf8(env, context->error_message.c_str(), context->error_message.length(), &error_msg_val);
        napi_value error_obj;
        napi_create_error(env, nullptr, error_msg_val, &error_obj); 
        napi_reject_deferred(env, context->deferred, error_obj);
    }
    napi_delete_async_work(env, context->work);
    delete context;
}

#define CREATE_ASYNC_AT_COMMAND_NAPI(NapiFuncName, AtCommandNameStr, CommandSetup) \
static napi_value NapiFuncName(napi_env env, napi_callback_info info) { \
    AsyncSendCommandContext* context = new AsyncSendCommandContext(); \
    context->original_command_name = AtCommandNameStr; \
    CommandSetup \
    napi_value promise; \
    napi_create_promise(env, &context->deferred, &promise); \
    napi_value resource_name; \
    napi_create_string_utf8(env, #NapiFuncName "Work", NAPI_AUTO_LENGTH, &resource_name); \
    napi_create_async_work(env, nullptr, resource_name, ExecuteSendCommand, CompleteSendCommand, context, &context->work); \
    napi_queue_async_work(env, context->work); \
    return promise; \
}



CREATE_ASYNC_AT_COMMAND_NAPI(SLEEnable, "SLEENABLE",
    context->command_to_send = "AT+SLEENABLE\r\n";
    context->read_timeout_ms = DEFAULT_AT_COMMAND_READ_TIMEOUT_MS;
)



static napi_value SLESetScanPar(napi_env env, napi_callback_info info) {
    size_t argc = 3;
    napi_value args[3];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    if (argc < 3) { napi_throw_error(env, nullptr, "SLESetScanPar: Insufficient arguments."); return nullptr;}
    int32_t scan_type, scan_interval_dec, scan_window_dec;
    napi_get_value_int32(env, args[0], &scan_type);
    napi_get_value_int32(env, args[1], &scan_interval_dec);
    napi_get_value_int32(env, args[2], &scan_window_dec);

    char command_buffer[100];
    snprintf(command_buffer, sizeof(command_buffer), "AT+SLESETSCANPAR=%d,0x%X,0x%X\r\n", scan_type, scan_interval_dec, scan_window_dec);

    AsyncSendCommandContext* context = new AsyncSendCommandContext();
    context->original_command_name = "SLESETSCANPAR";
    context->command_to_send = command_buffer;
    context->read_timeout_ms = DEFAULT_AT_COMMAND_READ_TIMEOUT_MS;

    napi_value promise;
    napi_create_promise(env, &context->deferred, &promise);
    napi_value resource_name;
    napi_create_string_utf8(env, "SLESetScanParWork", NAPI_AUTO_LENGTH, &resource_name);
    napi_create_async_work(env, nullptr, resource_name, ExecuteSendCommand, CompleteSendCommand, context, &context->work);
    napi_queue_async_work(env, context->work);
    return promise;
}



CREATE_ASYNC_AT_COMMAND_NAPI(SLEStartScan, "SLESTARTSCAN",
    context->command_to_send = "AT+SLESTARTSCAN\r\n";
    context->read_timeout_ms = DEFAULT_AT_COMMAND_READ_TIMEOUT_MS;
)



CREATE_ASYNC_AT_COMMAND_NAPI(SLEStopScan, "SLESTOPSCAN",
    context->command_to_send = "AT+SLESTOPSCAN\r\n";
    context->read_timeout_ms = DEFAULT_AT_COMMAND_READ_TIMEOUT_MS;
)



static napi_value SLEConn(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (argc < 2) { napi_throw_error(env, nullptr, "SLEConn: Insufficient arguments."); return nullptr;}
    int32_t addr_type;
    napi_get_value_int32(env, args[0], &addr_type);
    std::string sle_addr = NapiStringToStdString(env, args[1]);
    
    char command_buffer[100];
    snprintf(command_buffer, sizeof(command_buffer), "AT+SLECONN=%d,%s\r\n", addr_type, sle_addr.c_str());

    AsyncSendCommandContext* context = new AsyncSendCommandContext();
    context->original_command_name = "SLECONN";
    context->command_to_send = command_buffer;
    context->read_timeout_ms = 3 * DEFAULT_AT_COMMAND_READ_TIMEOUT_MS;

    napi_value promise;
    napi_create_promise(env, &context->deferred, &promise);
    napi_value resource_name;
    napi_create_string_utf8(env, "SLEConnWork", NAPI_AUTO_LENGTH, &resource_name);
    napi_create_async_work(env, nullptr, resource_name, ExecuteSendCommand, CompleteSendCommand, context, &context->work);
    napi_queue_async_work(env, context->work);
    return promise;
}



CREATE_ASYNC_AT_COMMAND_NAPI(SSAPCRegCbk, "SSAPCREGCBK",
    context->command_to_send = "AT+SSAPCREGCBK\r\n";
    context->read_timeout_ms = DEFAULT_AT_COMMAND_READ_TIMEOUT_MS;
)



static napi_value SSAPCFndStru(napi_env env, napi_callback_info info) {
    size_t argc = 6;
    napi_value args[6];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (argc < 6) { napi_throw_error(env, nullptr, "SSAPCFndStru: Insufficient arguments."); return nullptr; }
    int32_t client_id, conn_id, type, start_hdl_dec, end_hdl_dec;
    napi_get_value_int32(env, args[0], &client_id);
    napi_get_value_int32(env, args[1], &conn_id);
    napi_get_value_int32(env, args[2], &type);
    std::string uuid_str = NapiStringToStdString(env, args[3]);
    napi_get_value_int32(env, args[4], &start_hdl_dec);
    napi_get_value_int32(env, args[5], &end_hdl_dec);

    char command_buffer[150];
    snprintf(command_buffer, sizeof(command_buffer), "AT+SSAPCFNDSTRU=%d,%d,%d,%s,%d,0x%X\r\n",
             client_id, conn_id, type, uuid_str.c_str(), start_hdl_dec, end_hdl_dec);

    AsyncSendCommandContext* context = new AsyncSendCommandContext();
    context->original_command_name = "SSAPCFNDSTRU";
    context->command_to_send = command_buffer;
    context->read_timeout_ms = 2 * DEFAULT_AT_COMMAND_READ_TIMEOUT_MS;

    napi_value promise;
    napi_create_promise(env, &context->deferred, &promise);
    napi_value resource_name;
    napi_create_string_utf8(env, "SSAPCFndStruWork", NAPI_AUTO_LENGTH, &resource_name);
    napi_create_async_work(env, nullptr, resource_name, ExecuteSendCommand, CompleteSendCommand, context, &context->work);
    napi_queue_async_work(env, context->work);
    return promise;
}



static napi_value SSAPCWriteCmd(napi_env env, napi_callback_info info) {
    size_t argc = 6;
    napi_value args[6];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (argc < 6) { napi_throw_error(env, nullptr, "SSAPCWriteCmd: Insufficient arguments."); return nullptr; }
    int32_t client_id, conn_id, handle, type, len;
    napi_get_value_int32(env, args[0], &client_id);
    napi_get_value_int32(env, args[1], &conn_id);
    napi_get_value_int32(env, args[2], &handle);
    napi_get_value_int32(env, args[3], &type);
    napi_get_value_int32(env, args[4], &len);
    std::string write_data_str = NapiStringToStdString(env, args[5]);

    char command_buffer[200];
    snprintf(command_buffer, sizeof(command_buffer),
             "AT+SSAPCWRITECMD=%d,%d,%d,%d,%d,0x%s\r\n-----data-----\r\n%s\r\n--------------\r\n",
             client_id, conn_id, handle, type, len, write_data_str.c_str(), write_data_str.c_str());
    





    AsyncSendCommandContext* context = new AsyncSendCommandContext();
    context->original_command_name = "SSAPCWRITECMD";
    context->command_to_send = command_buffer;
    context->read_timeout_ms = 2 * DEFAULT_AT_COMMAND_READ_TIMEOUT_MS;

    napi_value promise;
    napi_create_promise(env, &context->deferred, &promise);
    napi_value resource_name;
    napi_create_string_utf8(env, "SSAPCWriteCmdWork", NAPI_AUTO_LENGTH, &resource_name);
    napi_create_async_work(env, nullptr, resource_name, ExecuteSendCommand, CompleteSendCommand, context, &context->work);
    napi_queue_async_work(env, context->work);
    return promise;
}



static napi_value SSAPCReadReq(napi_env env, napi_callback_info info) {
    size_t argc = 4;
    napi_value args[4];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (argc < 4) { napi_throw_error(env, nullptr, "SSAPCReadReq: Insufficient arguments."); return nullptr; }
    int32_t client_id, conn_id, handle, type;
    napi_get_value_int32(env, args[0], &client_id);
    napi_get_value_int32(env, args[1], &conn_id);
    napi_get_value_int32(env, args[2], &handle);
    napi_get_value_int32(env, args[3], &type);
    
    char command_buffer[150];
    snprintf(command_buffer, sizeof(command_buffer), "AT+SSAPCREADREQ=%d,%d,%d,%d\r\n",
             client_id, conn_id, handle, type);

    AsyncSendCommandContext* context = new AsyncSendCommandContext();
    context->original_command_name = "SSAPCREADREQ";
    context->command_to_send = command_buffer;
    context->read_timeout_ms = 3 * DEFAULT_AT_COMMAND_READ_TIMEOUT_MS; 

    napi_value promise;
    napi_create_promise(env, &context->deferred, &promise);
    napi_value resource_name;
    napi_create_string_utf8(env, "SSAPCReadReqWork", NAPI_AUTO_LENGTH, &resource_name);
    napi_create_async_work(env, nullptr, resource_name, ExecuteSendCommand, CompleteSendCommand, context, &context->work);
    napi_queue_async_work(env, context->work);
    return promise;
}




EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    g_serialPortInstance = nullptr; 
    g_scanParamsSuccessfullySet = false; 


    napi_property_descriptor desc[] = {
        {"connectSerial", nullptr, ConnectSerial, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"disconnectSerial", nullptr, DisconnectSerial, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"sleEnable", nullptr, SLEEnable, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"sleSetScanPar", nullptr, SLESetScanPar, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"sleStartScan", nullptr, SLEStartScan, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"sleStopScan", nullptr, SLEStopScan, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"sleConn", nullptr, SLEConn, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"ssapcRegCbk", nullptr, SSAPCRegCbk, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"ssapcFndStru", nullptr, SSAPCFndStru, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"ssapcWriteCmd", nullptr, SSAPCWriteCmd, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"ssapcReadReq", nullptr, SSAPCReadReq, nullptr, nullptr, nullptr, napi_default, nullptr}
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module nearlink_module = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry", 
    .nm_priv = ((void*)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterNearLinkModule(void) {
    napi_module_register(&nearlink_module);
}